package top.xzxsrq.web.download;

import cn.hutool.core.util.URLUtil;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializerFeature;
import org.htmlunit.html.DomElement;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.FrameWindow;
import org.htmlunit.html.HtmlPage;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.extern.log4j.Log4j2;
import top.xzxsrq.common.utils.DistinctKeyUtilZX;
import top.xzxsrq.common.utils.FileUtilsZX;
import top.xzxsrq.common.utils.NumberUtilsZX;
import top.xzxsrq.ffmpeg.FFmpegUtils;
import top.xzxsrq.utils.PathUtils;
import top.xzxsrq.utils.StringUtilsZX;
import top.xzxsrq.web.download.entity.CheckFile;
import top.xzxsrq.web.download.entity.PageInfo;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.net.URLDecoder;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Collections;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.stream.Collectors;

@Log4j2
public class FeiJiSuD extends CommonD {

    private CopyOnWriteArrayList<String> pageQueryList;
    private boolean pageQueryNotInit = true;
    private List<CheckFile> checkFileList;
    private String getCheckJsonFileFullPath;

    public void init(String saveDir, String initPageQuery) {
        pageQueryList = new CopyOnWriteArrayList<>(Collections.singletonList(initPageQuery));
        List<CheckFile> checkFileList1;
        getCheckJsonFileFullPath = saveDir + File.separator + checkJsonFile;
        try {
            String s = FileUtilsZX.readFileToString(new File(getCheckJsonFileFullPath), StandardCharsets.UTF_8);
            if (StringUtilsZX.isEmpty(s)) {
                checkFileList1 = new LinkedList<>();
            } else {
                checkFileList1 = JSON.parseArray(s, CheckFile.class);
            }
        } catch (IOException e) {
            log.error("获取JSON检查文件出错");
            writeJSON();
            checkFileList1 = new LinkedList<>();
        }
        checkFileList = checkFileList1;
    }

    /**
     * 判断url是否是飞极速的
     *
     * @param pathUrl
     * @return
     */
    @Override
    public boolean isDownloadPage(String pathUrl) {
        URL url = URLUtil.url(pathUrl);
        String host = url.getHost();
        if (host.contains("feijisu")) {
            return true;
        }
        return false;
    }

    @Override
    public String getDownloadType() {
        return "飞极速下载";
    }

    @Override
    public String getPageDownLoadName(List<FrameWindow> iframes) {
        for (FrameWindow iframe : iframes) {
            HtmlPage enclosingPage = iframe.getEnclosingPage();
            DomNode domNode = enclosingPage.querySelector("a.current");
            setPageQueryList(enclosingPage);
            if (domNode != null) {
                return domNode.getVisibleText();
            }
            HtmlPage enclosedPage = (HtmlPage) iframe.getEnclosedPage();
            String pageVideoName = getPageDownLoadName(enclosedPage.getFrames());
            if (pageVideoName != null) {
                return pageVideoName;
            }
        }
        return null;
    }

    /**
     * 清洗页面里面a标签？参数
     *
     * @param enclosingPage
     */
    public void setPageQueryList(HtmlPage enclosingPage) {
        // 已经填充过了
        if (pageQueryList.size() > 1) {
            return;
        }
        List<String> href = enclosingPage.querySelectorAll("a.twidth")
                .stream()
                .map((i) -> (DomElement) i)
                .map(i -> i.getAttribute("href"))
                .map(i -> {
                    String[] split = i.split("\\?");
                    if (split.length >= 2) {
                        return split[1];
                    }
                    return "";
                })
                .filter(StringUtilsZX::isNotEmpty)
                .filter(DistinctKeyUtilZX.distinctByKey(i -> i))
                .map(i -> "?" + i)
                .collect(Collectors.toList());
        pageQueryList.addAll(href);
        List<String> collect = pageQueryList.stream()
                .filter(DistinctKeyUtilZX.distinctByKey(i -> i))
                .collect(Collectors.toList());
        pageQueryList.clear();
        pageQueryList.addAll(collect);
        log.info("现在可以用的全部参数：" + pageQueryList);
        pageQueryNotInit = false;
    }


    public void downloadPages(
            String saveDir, // 文件保存位置
            int videoStart, // 视频开始位置
            int videoEnd, // 视频结束位置
            int nameLength, // 名字的长度
            String templatePageUrl, // 下载模板url http://m.feijisu36.com/zongyi/90182/${}.html${}
            String initPageQuery // ?qp=ff
    ) throws InterruptedException, ExecutionException {
        init(saveDir, initPageQuery);
        int checkNumber = 3;
        int count = 0;
        while (checkNumber > 0) {
            boolean checkFileFullOk;
            List<Future<Void>> futures = new LinkedList<>();
            for (int i = videoStart; i <= videoEnd; i++) {
                String s = NumberUtilsZX.doHandleZero(i, nameLength);
                String systemPath = PathUtils.pathStandardization(saveDir + "/" + s + ".mp4");
                // 直接检查本地文件是否和记录值一样
                CheckFile checkFile1 = checkLocalFile(systemPath);
                if (checkFile1 != null) {
                    FileUtilsZX.deleteFile(systemPath);
                    continue;
                }
                FeiJiSuDT feiJiSuDT = new FeiJiSuDT();
                feiJiSuDT.setSystemPath(systemPath);
                feiJiSuDT.setFeiJiSuD(this);
                feiJiSuDT.setDownloadId(this.downloadTaskId);
                feiJiSuDT.setI(i);
                feiJiSuDT.setIsFirstDownloadPage(videoStart == i);
                feiJiSuDT.setTemplatePageUrl(templatePageUrl);
                feiJiSuDT.setDownloadType(this.getDownloadType());
                futures.add(DownloadManager.submit(feiJiSuDT));
            }
            // 阻塞主线程
            for (Future<Void> future : futures) {
                future.get();
            }
            log.info("{}:{}:检查完整性第{}次", getDownloadType(), downloadTaskId, ++count);
            // 检查下载完整性
            checkFileFullOk = checkDownloadFullOk();
            if (checkFileFullOk) {
                log.info("{}:{}:下载完整", getDownloadType(), downloadTaskId);
                break;
            }
            checkNumber--;
        }
        log.info("{}:{}：检查次数{}：系统设置检查次数：{}：完整性：{}", getDownloadType(), downloadTaskId, count, checkNumber, checkDownloadFullOk());
    }


    // 检查下载完整性
    public boolean checkDownloadFullOk() {
        for (CheckFile checkFile : checkFileList) {
            boolean b = FileUtilsZX.notExists(new File(checkFile.getLocalPath()));
            if (b) {
                return false;
            }
        }
        return true;
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        FFmpegUtils.FFMPEG = "ffmpeg";
        FeiJiSuD feiJiSuD = new FeiJiSuD();
        feiJiSuD.downloadTaskId = "本地下载";
        feiJiSuD.downloadPages(
                PathUtils.pathStandardization("E:\\myService\\windows-amd64-filebrowser\\filebrowserData\\动漫/新干线变形机器人 改变世界"),
                1,
                1,
                4,
                "http://feijisu36.com/acg/91142/${}.html${}",
                ""
        );
        DownloadManager.threadPoolExecutor.shutdown();
        CommonD.close();
    }

    public static boolean openPage(CheckFile checkFile) {
        return checkFile == null ||
                checkFile.getDownloadUrl() == null ||
                checkFile.getHttpVideoTimeLength() == null ||
                checkFile.getDownloadName() == null;
    }

    public CheckFile checkLocalFile(String systemPath) {
        CheckFile checkFile = getCheckFileBySystemPath(systemPath);
        if (checkFile != null) {
            String localPath = checkFile.getLocalPath();
            Float videoSeconds = FFmpegUtils.getVideoSeconds(localPath);
            boolean b = FFmpegUtils.timeEquals(videoSeconds, checkFile.getHttpVideoTimeLength());
            if (b) {
                log.info(systemPath + ": 本地文件校验时间相等");
                String s = localPath + ".downloading";
                FileUtilsZX.deleteFile(s);
                return checkFile;
            }
        }
        return null;
    }

    public CheckFile getCheckFileBySystemPath(String systemPath) {
        return checkFileList
                .stream()
                .filter(i -> PathUtils.isEquals(systemPath, i.getSystemPath()))
                .findFirst()
                .orElse(null);
    }

    public boolean checkTimeIsOk(CheckFile checkFile) {
        boolean b = checkTimeIsOkNoLog(checkFile);
        String url = checkFile.getDownloadUrl();
        String path = checkFile.getLocalPath();
        if (b) {
            String s = StringUtilsZX.strFormat("视频时长相等：${},${}", url, path);
            log.info(s);
        } else {
            String s = StringUtilsZX.strFormat("视频时长不相等：${},${}", url, path);
            log.error(s);
        }
        return b;
    }

    private boolean checkTimeIsOkNoLog(CheckFile checkFile) {
        addOrUpdate(checkFile);
        String url = checkFile.getDownloadUrl();
        String path = checkFile.getLocalPath();
        Float videoSeconds1 = FFmpegUtils.getVideoSeconds(path);
        Float videoSeconds = FFmpegUtils.getVideoSeconds(url);
        checkFile.setHttpVideoTimeLength(videoSeconds);
        addOrUpdate(checkFile);
        return FFmpegUtils.timeEquals(videoSeconds, videoSeconds1);
    }

    public synchronized boolean addOrUpdate(CheckFile c) {
        CheckFile checkFile = getCheckFileBySystemPath(c.getLocalPath());
        if (checkFile == null) {
            checkFile = getCheckFileUrl(c.getDownloadUrl());
        }
        if (checkFile != null) {
            removeChecks(checkFile);
        }
        checkFileList.add(c);
        return writeJSON();
    }

    public CheckFile getCheckFileUrl(String url) {
        return checkFileList
                .stream()
                .filter(i -> StringUtilsZX.equals(url, i.getDownloadUrl()))
                .findFirst()
                .orElse(null);
    }

    public synchronized List<CheckFile> removeChecks(CheckFile c) {
        log.info("要进行移除的内容 = " + c);
        List<Integer> integerList = new LinkedList<>();
        for (int i = 0; i < checkFileList.size(); i++) {
            CheckFile checkFile = checkFileList.get(i);
            if (checkFile.equals(c)) {
                integerList.add(i);
            }
        }
        List<CheckFile> removeCheck = new LinkedList<>();
        for (int i = integerList.size() - 1; i >= 0; i--) {
            removeCheck.add(checkFileList.remove(integerList.get(i).intValue()));
        }
        return removeCheck;
    }

    public synchronized boolean writeJSON() {
        File file = FileUtilsZX.createFile(getCheckJsonFileFullPath);
        try {
            if (checkFileList == null) {
                FileUtilsZX.write(file, "[]", StandardCharsets.UTF_8);
            } else {
                FileUtilsZX.write(file, JSON.toJSONString(checkFileList, SerializerFeature.PrettyFormat), StandardCharsets.UTF_8);
            }
            return true;
        } catch (IOException ex) {
            log.error("写入JSON文件错误");
        }
        return false;
    }

    @EqualsAndHashCode(callSuper = true)
    @Data
    public static class FeiJiSuDT extends VideoD.VideoDT {
        protected String systemPath;
        protected FeiJiSuD feiJiSuD;
        protected int i;
        protected String templatePageUrl;
        protected Boolean isFirstDownloadPage;

        public void overCall() throws IOException, InterruptedException {
            if (this.isCanStop()) {
                this.destroy();
                return;
            }
            CheckFile checkFile = new CheckFile();
            checkFile.setSystemPath(systemPath);
            CheckFile httpPathBySystemPath = feiJiSuD.getCheckFileBySystemPath(systemPath);
            String feiJiSuM3U8Url;
            // 记录页面参数是否已经初始化，用于使用第一个页面可以进入其他参数的页面进行判断
            boolean pageQueryNotInit = feiJiSuD.pageQueryNotInit;
            int loop = isFirstDownloadPage ? 1 : 0; // 首页就是要多循环一次
            boolean breakPageQueryList = false;
            do {
                if (this.isCanStop()) {
                    this.destroy();
                    return;
                }
                for (String s1 : feiJiSuD.pageQueryList) {
                    if (this.isCanStop()) {
                        this.destroy();
                        return;
                    }
                    // 已经初始化且只有第一个页面需要判断不能重新使用第一个参数进行爬取页面
                    if (isFirstDownloadPage && !pageQueryNotInit && s1.equals(feiJiSuD.pageQueryList.get(0))) {
                        continue;
                    }
                    log.info("当前使用的页面参数：{}", s1);
                    // 一个页面尝试3次
                    for (int j = 0; j < 3; j++) {
                        if (this.isCanStop()) {
                            this.destroy();
                            return;
                        }
                        if (openPage(httpPathBySystemPath)) {
                            String pathUrl = StringUtilsZX.strFormat(templatePageUrl, String.valueOf(i), s1);
                            checkFile.setDownloadPageUrl(pathUrl);
                            PageInfo pageInfo = feiJiSuD.getPageInfo(pathUrl, this, new String[]{".mp4", ".m3u8"}, true);
                            if (this.isCanStop()) {
                                this.destroy();
                                return;
                            }
                            feiJiSuM3U8Url = pageInfo.getDownloadUrl();
                            checkFile.setDownloadName(pageInfo.getDownloadName());
                            pageQueryNotInit = feiJiSuD.pageQueryNotInit;
                        } else {
                            feiJiSuM3U8Url = httpPathBySystemPath.getDownloadUrl();
                            checkFile.setDownloadName(httpPathBySystemPath.getDownloadName());
                            checkFile.setDownloadPageUrl(httpPathBySystemPath.getDownloadPageUrl());
                            pageQueryNotInit = false;
                        }
                        if (StringUtilsZX.isNotEmpty(feiJiSuM3U8Url)) {
                            log.info("feiJiSuM3U8Url = " + feiJiSuM3U8Url);
                            String[] https = feiJiSuM3U8Url.split("http"); // TODO 解析url
                            // 最后真正的下载地址
                            String m3u8Url = URLDecoder.decode("http" + https[https.length - 1], String.valueOf(Charset.defaultCharset()));
                            checkFile.setDownloadUrl(m3u8Url);
                            if (feiJiSuD.checkTimeIsOk(checkFile)) {
                                FileUtilsZX.deleteFile(systemPath);
                                breakPageQueryList = true;
                                break;
                            } else {
                                // 调用父级下载功能
                                PageInfo info = new PageInfo();
                                info.setDownloadType(this.getDownloadType());
                                info.setDownloadName(checkFile.getDownloadName());
                                info.setDownloadPageUrl(checkFile.getDownloadPageUrl());
                                info.setDownloadUrl(checkFile.getDownloadUrl());
                                setSavePath(checkFile.getLocalPath());
                                setPageInfo(info);
                                try {
                                    downloadFunc();
                                    FileUtilsZX.deleteFile(systemPath);
                                    breakPageQueryList = true;
                                    break;
                                } catch (Exception e) {
                                    feiJiSuD.removeChecks(checkFile);
                                    feiJiSuD.writeJSON();
                                    Thread.sleep(2000);
                                    throw e;
                                }
                            }
                        } else {
                            Thread.sleep(2000);
                        }
                    }
                    if (breakPageQueryList) {
                        break;
                    }
                }
                if (breakPageQueryList) {
                    break;
                }
                loop--;
            } while (loop == 0);
        }

        @Override
        public JSONObject getDownloadInfo() {
            JSONObject result = new JSONObject();
            result.put("downloadTaskId", feiJiSuD.downloadTaskId);
            result.put("pageInfo", this.getPageInfo());
            return result;
        }
    }
}
